/*
 * Copyright (C) 2013 Allwinnertech, kevin.z.m <kevin@allwinnertech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
#include <linux/clk-private.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/clk/sunxi.h>
#include <mach/sys_config.h>
#include "clk-sunxi.h"
#include "clk-factors.h"
#include "clk-periph.h"
#ifdef CONFIG_ARCH_SUN9IW1
#include "clk-sun9iw1.h"
#endif
#ifdef CONFIG_ARCH_SUN8IW6
#include "clk-sun8iw6.h"
#endif
#include <linux/arisc/arisc.h>

#define CK32K_OUT_CTRL1  0xC1
#define CK32K_OUT_CTRL2  0xC2
#define CK32K_OUT_CTRL3  0xC3

struct periph_init_data {
    const char          *name;
    unsigned long       flags;
    const char          **parent_names;
    int                 num_parents;
    struct sunxi_clk_periph *periph;
};

/*
SUNXI_CLK_PERIPH(name,       mux_reg,         mux_shift, mux_width, div_reg,        div_mshift, div_mwidth, div_nshift, div_nwidth, gate_flags, enable_reg,     reset_reg, bus_gate_reg, drm_gate_reg, enable_shift, reset_shift, bus_gate_shift, dram_gate_shift,lock,com_gate,com_gate_off)
*/
SUNXI_CLK_PERIPH(ac10032k1, CK32K_OUT_CTRL1, 4,         1,         CK32K_OUT_CTRL1,5,          3,          1,          3,          0,          CK32K_OUT_CTRL1,0,         0,            0,            0,            0,           0,              0,              NULL,NULL,             0);
SUNXI_CLK_PERIPH(ac10032k2, CK32K_OUT_CTRL2, 4,         1,         CK32K_OUT_CTRL2,5,          3,          1,          3,          0,          CK32K_OUT_CTRL2,0,         0,            0,            0,            0,           0,              0,              NULL,NULL,             0);
SUNXI_CLK_PERIPH(ac10032k3, CK32K_OUT_CTRL3, 4,         1,         CK32K_OUT_CTRL3,5,          3,          1,          3,          0,          CK32K_OUT_CTRL3,0,         0,            0,            0,            0,           0,              0,              NULL,NULL,             0);
static const char *ac10032k_parents[]  = {"32k_rtc", "4m_adda"};
#ifndef CONFIG_ARCH_SUN8IW6
static struct periph_init_data sunxi_ac100_init[] = {
    {"ac10032k1", CLK_GET_RATE_NOCACHE,ac10032k_parents,     ARRAY_SIZE(ac10032k_parents),     &sunxi_clk_periph_ac10032k1},
    {"ac10032k2", CLK_GET_RATE_NOCACHE,ac10032k_parents,     ARRAY_SIZE(ac10032k_parents),     &sunxi_clk_periph_ac10032k2},
    {"ac10032k3", CLK_GET_RATE_NOCACHE,ac10032k_parents,     ARRAY_SIZE(ac10032k_parents),     &sunxi_clk_periph_ac10032k3},
};
#else 
/*add flag CLK_IGNORE_SYNCBOOT for SUN8IW6 platform*/
static struct periph_init_data sunxi_ac100_init[] = {
    {"ac10032k1", CLK_GET_RATE_NOCACHE|CLK_IGNORE_SYNCBOOT,ac10032k_parents,     ARRAY_SIZE(ac10032k_parents),     &sunxi_clk_periph_ac10032k1},
    {"ac10032k2", CLK_GET_RATE_NOCACHE|CLK_IGNORE_SYNCBOOT,ac10032k_parents,     ARRAY_SIZE(ac10032k_parents),     &sunxi_clk_periph_ac10032k2},
    {"ac10032k3", CLK_GET_RATE_NOCACHE|CLK_IGNORE_SYNCBOOT,ac10032k_parents,     ARRAY_SIZE(ac10032k_parents),     &sunxi_clk_periph_ac10032k3},
};
#endif

static unsigned int ac100_m_factor[]={1,2,4,8,16,32,64,122};
static unsigned int ac100_n_factor[]={1,2,4,8,16,32,64,122};

static unsigned long sunxi_ac100_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
    unsigned long reg;
    struct sunxi_clk_periph *periph = to_clk_periph(hw);
    struct sunxi_clk_periph_div *divider = &periph->divider;
    unsigned long div, div_m = 0, div_n = 0;
    u64 rate = parent_rate;
    if(!divider->reg)
        return parent_rate;

    reg = periph_readl(periph,divider->reg);
    if(divider->mwidth)
        div_m = GET_BITS(divider->mshift, divider->mwidth, reg);
    if(divider->nwidth)
        div_n = GET_BITS(divider->nshift, divider->nwidth, reg);
    if(reg & 0x100)
        div = ac100_m_factor[div_m]*ac100_n_factor[div_n];
    else
        div = ac100_n_factor[div_n];
    do_div(rate, div);
    return rate;
}
static long sunxi_ac100_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate)
{
    int i,j,m_max;
    int m=0,n=0;
    unsigned long cur_rate=0,new_rate=0;
    unsigned long cur_delta,new_delta;
    u32 parent_rate = *prate;
    if(*prate == 4000000)
        m_max =1;
    else
        m_max = 8;

    for(i=0;i<m_max;i++)
        for(j=0;j<8;j++)
        {
            new_rate = parent_rate/(ac100_m_factor[i]*ac100_n_factor[j]);
            new_delta = (new_rate >rate)?(new_rate-rate):(rate-new_rate);
            cur_delta = (cur_rate >rate)?(cur_rate-rate):(rate-cur_rate);
            if(new_delta < cur_delta)
            {
                cur_rate = new_rate;
                m =i;
                n = j;
            }
        }
       return cur_rate;
}
static int __sunxi_clk_periph_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate)
{
    struct sunxi_clk_periph *periph = to_clk_periph(hw);
    struct sunxi_clk_periph_div *divider = &periph->divider;
    int i,j,m_max;
    int m=0,n=0;
    unsigned long cur_rate=0,new_rate=0;
    unsigned long cur_delta,new_delta;
    u32 reg;
    if(parent_rate == 4000000)
        m_max =1;
    else
        m_max = 8;
    for(i=0;i<m_max;i++)
        for(j=0;j<8;j++)
        {
            new_rate = parent_rate/(ac100_m_factor[i]*ac100_n_factor[j]);
            new_delta = (new_rate >rate)?(new_rate-rate):(rate-new_rate);
            cur_delta = (cur_rate >rate)?(cur_rate-rate):(rate-cur_rate);
            if(new_delta < cur_delta)
            {
                cur_rate = new_rate;
                m =i;
                n = j;
            }
        }
    reg = periph_readl(periph,divider->reg);
    if(divider->mwidth)
        reg = SET_BITS(divider->mshift, divider->mwidth, reg, m);
    if(divider->nwidth)
        reg = SET_BITS(divider->nshift, divider->nwidth, reg, n);
    periph_writel(periph,reg, divider->reg);
    return 0;
}

static u32 ac100_readl(void __iomem * reg)
{
    arisc_rsb_block_cfg_t rsb_data;
    unsigned char addr;
    unsigned int val;

    addr = (unsigned char)((unsigned long)reg);
    rsb_data.len = 1;
    rsb_data.datatype = RSB_DATA_TYPE_HWORD;
    rsb_data.msgattr = ARISC_MESSAGE_ATTR_SOFTSYN;
    rsb_data.devaddr = RSB_RTSADDR_AC100;
    rsb_data.regaddr =  &addr;
    rsb_data.data = &val;
    /* read registers */
    if (arisc_rsb_read_block_data(&rsb_data))
        pr_err("%s(%d) err: read reg-0x%x failed", __func__, __LINE__, (unsigned int __force)reg);
    return val;
}

static void ac100_writel(u32 val,void __iomem * reg)
{
    arisc_rsb_block_cfg_t rsb_data;
    u16 data = (u16)val;

    rsb_data.len = 1;
    rsb_data.datatype = RSB_DATA_TYPE_HWORD;
    rsb_data.msgattr = ARISC_MESSAGE_ATTR_SOFTSYN;
    rsb_data.devaddr = RSB_RTSADDR_AC100;
    rsb_data.regaddr = (unsigned char *)&reg;
    rsb_data.data = (unsigned int *)&data;

#ifdef CONFIG_ARCH_SUN9IW1
    if(((unsigned int __force)reg == 0xc1) && (!(val&0x01)))
    {
        pr_err("Warning!!! %s skip write %x to reg %x\n", __func__,val,(unsigned int __force)reg);
        return;
    }
#endif
    /* write registers */
    if (arisc_rsb_write_block_data(&rsb_data))
        pr_err("%s(%d) err: write reg-0x%x failed", __func__, __LINE__, (unsigned int __force)reg);
    return;
}
static struct clk_ops ac100_clkops;
static struct sunxi_reg_ops ac100_regops;
extern void sunxi_clk_get_periph_ops(struct clk_ops* ops);

static int __init sunxi_init_ac100_clocks(void)
{
	struct clk *clk;
    int i;
    struct periph_init_data *periph;

    if (arisc_rsb_set_rtsaddr(RSB_DEVICE_SADDR7, RSB_RTSADDR_AC100)) {
        pr_err("%s err: config codec failed\n", __func__);
        return -1;
    }

    //reigster source for AC100 32K
    clk = clk_register_fixed_rate(NULL, "32k_rtc", NULL, CLK_IS_ROOT, 32768);
    clk_register_clkdev(clk, "32k_rtc", NULL);
    clk = clk_register_fixed_rate(NULL, "4m_adda", NULL, CLK_IS_ROOT, 4000000);
    clk_register_clkdev(clk, "4m_adda", NULL);

    sunxi_clk_get_periph_ops(&ac100_clkops);
    ac100_clkops.prepare = ac100_clkops.enable;
    ac100_clkops.unprepare = ac100_clkops.disable;
    ac100_clkops.enable = NULL;
    ac100_clkops.disable = NULL;
    ac100_clkops.recalc_rate = sunxi_ac100_recalc_rate;
    ac100_clkops.round_rate = sunxi_ac100_round_rate;
    ac100_clkops.set_rate = __sunxi_clk_periph_set_rate;
    ac100_regops.reg_writel = ac100_writel;
    ac100_regops.reg_readl = ac100_readl;
    /* register AC100 clock */
    for(i=0; i<ARRAY_SIZE(sunxi_ac100_init); i++)
    {
        periph = &sunxi_ac100_init[i];
        periph->periph->priv_clkops = &ac100_clkops;
        periph->periph->priv_regops = &ac100_regops;
        clk = sunxi_clk_register_periph(periph->name, periph->parent_names,
                        periph->num_parents,periph->flags, NULL, periph->periph);
        clk_register_clkdev(clk, periph->name, NULL);
    }
#ifndef CONFIG_ARCH_SUN8IW6
    //Sync enable count for Ac100
    for(i=0; i<ARRAY_SIZE(sunxi_ac100_init); i++)
    {
		struct clk *parent;
        periph = &sunxi_ac100_init[i];
        clk = clk_get(NULL,periph->name);
		if(!clk || IS_ERR(clk))
            continue;
        if((!clk->prepare_count) && (!clk->enable_count) && clk->ops->is_enabled(clk->hw))
        {
            clk->prepare_count++;
            clk->enable_count++;
            parent = clk->parent;
            while(parent)
            {
                parent->enable_count++;
                parent->prepare_count++;
                parent = parent->parent;
            }
        }
        clk_put(clk);
    }
#endif
    return 0;
}
subsys_initcall_sync(sunxi_init_ac100_clocks);


